I conduct a STM (Strucutral Topic Model) estimation on a sample of 11,919 online news articles from seven news provider about domestic politics: Bild.de, DIE WELT, FOCUS ONLINE, SPIEGEL ONLINE, Stern.de, ZEIT ONLINE, Tagesschau.de. The articles are dated from 01.06.2017 to 31.12.2017 (German federal elections took place on 24th of September 2017.). I first extract all online articles using the the Eventregistry API. Then all articles from the section “domestic policy” are filtered by checking the URL structure.

To discover the latent topics in the corpus, the structural topic modeling (STM) developed by Roberts (2016) is applied. The STM is an unsupervised machine learning approach that models topics as multinomial distributions of words and documents as multinomial distributions of topics, allowing to incorporate external variables that effect both, topical content and topical prevalence. I will included the news provider as a control for both the topical content and the topical prevalence. Additional, the month an article was published is included as a control for the topical prevalence. The number of topics is set to 35.

Distribution of articles

The Figures below show the distribution of the number of articles from the respective news sources by date. There is a high peak around the federal elections on September, 24th.

ggsave({
  btw %>%
  ggplot(aes(site)) +
  geom_bar(fill=col[1], alpha= .8) +
  labs(x="", y="Number of articles") +
  theme(
      legend.position   = "none"
    )
  
},
filename = "../figs/bar.png", device = "png", 
width = 6, height = 4,
        dpi = 600)
plot1

plot1

ggsave({
  btw %>%
  group_by(date) %>%
  dplyr::summarise(obs = n()) %>%
  ggplot(aes(date, obs)) +
  geom_line(color=col[3]) +
  geom_vline(aes(xintercept=as.Date("2017-09-24")),
             linetype = 2, color=col[2]) +
  scale_color_manual(values = col) +
  labs(x="", y="number of articles",color="") +
  scale_x_date(breaks = date_breaks("1 month"), labels=date_format("%B", tz="CET")) +
  theme(
      legend.position   = "none",
      axis.title.x      = element_blank(),
      axis.text       = element_text(size = 8)
    )
},
filename = "../figs/timeline.png", device = "png",width = 6, height = 4,
dpi = 600
)
plot1

plot1

2. Model Results

Label topics

In order to improve readability and traceability, I assign a shorter name to the topics based on the most common words. The plotQuote function allows to inspect die most common words of a topic for each covariate.

topics <- matrix(c(1, "SPD, M.Schulz", 2, "A.Merkel vs. Schulz", 3, "Jamaica coalition", 4, "Diesel scandal", 5, "H.Kohl", 6, "A.Merkel", 7, "Federal Election Results", 8, "Seehover vs. Söder, CSU", 9, "EU policies, refugee policy", 10, "German Parliament, parliamentarians", 11, "Mix: DIE LINKE, W.Schäuble", 12, "Refugees, deportation", 13, "Mix: Schröder, IT Topics", 14, "B90/Die Grünen", 15, "USA, Russia, Israel", 16, "Great coalition", 17, "A.Weidel (AfD)", 18, "Budget statistics, Welfare policy", 19, "Process Error", 20, "Federal Constitutional Court, Ministry of the Interior", 21, "German armed forces, Mali", 22, "Mix: Weather, DB, nuclear transport, ...", 23, "Elections in Niedersachsen", 24, "CDU / CSU", 25, "Mix: political talkshows, Berxit, Macron", 26, "Mix: F.-W.Steinmeier, Constitution, H.Maas", 27, "public statistics", 28, "Election polls", 29, "F.Petry, Gauland (AfD)", 30, "Mix: NSU, sexism", 31, "German armed forces, V.d.Leyen", 32, "G20 in Hamburg", 33, "Turkey, S.Gabriel", 34, "police reports, terror (left-, right-wing, IS)", 35, "Seehofer, refugee cap"), ncol=2, byrow=T)

topics.df <- as.data.frame(topics) %>%
  transmute(topic_name = paste(V1, V2, sep=": "),
         topic = 1:k) 

Next, we can assign a topic to each document (topic with highest postertior gamma)

# Document-topic probabilities
stmOut %>% tidy("theta") -> theta

top_topics <- theta %>% 
  group_by(document) %>%
  mutate(therank = rank(-gamma)) %>%
  filter(therank == 1) %>%
  select(- therank)

btw.2 <- btw %>%
  mutate(document = articleID) %>%
  merge(.,top_topics, by="document") %>%
  ## Combine with Topic label
  merge(., topics.df, by="topic") %>%
  mutate(allocation = 1) 
How is the top-gamma value distributed among the corpus?
btw.2 %>%
  ggplot(aes(gamma)) +
  geom_density(color = col[3],
               fill = col[3], alpha=.7) +
  labs(x="Distribution of gamma value")

Inspect the unclear topics

3.1. Topic proportions

In order to get an initial overview of the results, the figure below displays the topics ordered by their expected frequency across the corpus (left side of the Figure) and the expected proportion of a topic in public media minus the expected proportion of topic use in private media (right side of the Figure). Thus topics more associated with public media appear to the right of zero. To assign a label to each topic, I looked at the most frequent words in that topic and the most representative articles.

keep <- seq(1:k)
Here, I create a Dataframe that contains the columns means of theta (per topic and covariate level)
frequency <- as.data.frame(colMeans(stmOut$theta)) %>%
  mutate(frequency = colMeans(stmOut$theta),
         topic = topics[,1],
         topic_name=paste(topics[,1],topics[,2], 
                          sep=": ")) %>%
  filter(topic %in% keep)

freq <- tapply(stmOut$theta[,1], stmOut$settings$covariates$betaindex, mean)
freq <- as.data.frame(freq) %>% 
    mutate(site=stmOut$settings$covariates$yvarlevels,
           topic = 1)

for(i in 2:k) {
  freq1 <- tapply(stmOut$theta[,i], stmOut$settings$covariates$betaindex, mean)
  freq1 <- as.data.frame(freq1) %>% 
    transmute(site=stmOut$settings$covariates$yvarlevels,
           topic = i,
           freq = freq1)
  
  freq <- rbind(freq, freq1)
}

freq <- freq %>%
  left_join(., topics.df, by = "topic") %>%
  #filter(topic %in% keep) %>%
  mutate(topic = topic_name) %>%
  left_join(., frequency %>% select(topic, frequency),
            by = "topic")

Next, we can plot the expected proportion of topic use in the overall corpus vs. the expected proportion of topic use for each medium.

p1 <- ggplot(frequency, aes(x=reorder(topic_name, frequency), y=frequency)) + 
    geom_col(fill=col[1], alpha=0.8) +
    coord_flip() +
    labs(x="", y="expected frequency") +
    theme(axis.text.x = element_text(size=8),
          axis.text.y = element_text(size=11),
          axis.title = element_text(size=10))

p1

p2 <- ggplot(freq, aes(reorder(topic_name,frequency), freq)) +
  geom_col(fill = col[3]) +
  coord_flip() +
  facet_wrap(~site, ncol = 7) +
  theme(
    #axis.text.y = element_blank(),
          axis.text.y = element_text(size=11),
          axis.title = element_text(size=10)) +
    labs(x="", y="expected frequency") 

p2 

Keep only those articles, that are clear & interessting to analyse.

keep <- c(1,16,24,29,3,6,8)

3.2. Difference in topic prevalence

To identify which of these differences is significant, the conditional expectation of topic prevalence for given document characteristics can be estimated. More specifically, I estimate a linear model, where the documents are observations, the dependent variable is the posterior probability of a topic and the covariates are the metadata of documents (see equation below).

\[ \theta_d=\alpha+\beta_1x_{ownership}+\beta_2x_{month}+\epsilon \]

The estimateEffect() uses the method of composition to incorporate uncertainty in the dependent variable, drawing a set of topic proportions from the variational posterior repeated times and compute the coefficients as the average over all results.

effect <- estimateEffect(c(1:k) ~site+s(month), stmOut, 
                         metadata = out$meta, uncertainty = "None")

Here, I create a dataframe that contains the results of the estimation.

tables <- vector(mode="list", length = length(effect$topics))

for (i in seq_along(effect$topics)) {
  sims <- lapply(effect$parameters[[i]], function(x) stm:::rmvnorm(500, x$est, x$vcov))
  sims <- do.call(rbind, sims)
  est <- colMeans(sims)
  se <- sqrt(apply(sims,2, stats::var))
  tval <- est/se
  rdf <- nrow(effect$data) - length(est)
  p <- 2*stats::pt(abs(tval), rdf, lower.tail = FALSE)
  topic <- i
  
  coefficients <- cbind(topic, est, se, tval, p)
  rownames(coefficients) <- attr(effect$parameters[[1]][[1]]$est, "names") 
  colnames(coefficients) <- c("topic", "Estimate", "Std. Error", "t value", "p")
  tables[[i]] <- coefficients
}

out1 <- list(call=effect$call, topics=effect$topics, tables=tables)

coeff <- as.data.frame(do.call(rbind,out1$tables))

coeff <- coeff %>% 
  mutate(parameter = rownames(coeff),
         parameter = gsub("site", "", parameter),
         parameter = ifelse(parameter == "s(month)1", "1_July", parameter),
         parameter = ifelse(parameter == "s(month)2", "2_August", parameter),
         parameter = ifelse(parameter == "s(month)3", "3_September", parameter),
         parameter = ifelse(parameter == "s(month)4", "4_October", parameter),
         parameter = ifelse(parameter == "s(month)5", "5_November", parameter),
         parameter = ifelse(parameter == "s(month)6", "6_December", parameter),
         signifcant = ifelse(p <= 0.5,"yes","no")) %>%
  left_join(., topics.df, by="topic")

The following figure shows the regression results for each news page. The coefficients indicate the deviation from the base value of Bild.de (keeping the month equal).

p1 <- coeff %>% 
  filter(topic %in% keep) %>%
  filter(parameter %in% stmOut$settings$covariates$yvarlevels) %>%
  ggplot(aes(x = reorder(topic_name,topic, decreasing=F), y = Estimate, fill=factor(signifcant))) +
  geom_col() +
  scale_fill_manual(values = col[c(2,1)]) +
  scale_x_discrete(position = "top") +
  coord_flip() +
  facet_wrap(~parameter, ncol = 8, scales = "free_x") +
  labs(x="", fill="significant at the 5% level") +
  theme(legend.position = "top", 
        axis.text.y = element_text(size=9),
        axis.text.x = element_text(angle=90)) 

p1

# ggsave(plot = p1, filename = "../figs/estimates.png", device = "png",width = 10, height = 7,
# dpi = 600)

The following figure shows the regression results for each month. The coefficients indicate the deviation from the base value of June 2017 (keeping the news source).

p1 <- coeff %>% 
  filter(topic %in% keep) %>%
  filter(!parameter %in% stmOut$settings$covariates$yvarlevels) %>%
  filter(parameter != "(Intercept)") %>%
  ggplot(aes(x = reorder(topic_name,topic, decreasing=F), y = Estimate, fill=factor(signifcant))) +
  geom_col() +
  scale_fill_manual(values = col[c(2,1)]) +
  scale_x_discrete(position = "top") +
  coord_flip() +
  facet_wrap(~parameter, ncol = 8, scales = "free_x") +
  labs(x="", fill="significant at the 5% level") +
  theme(legend.position = "top", 
        axis.text.y = element_text(size=9),
        axis.text.x = element_text(angle=90)) 

p1

Sentiment analysis

The idea of Sentiment analysis is to determine the attitude of a writer through online text data toward certain topic or the overall tonality of a document.

Lexical or “bag-ofwords” approaches are commonly used. In that approach, the researcher provides pre-defined dictionaries (lists) of words associated with a given emotion, such as negativity. The target text is then deconstructed into individual words (or tokens) and the frequencies of words contained in a given dictionary are then calculated.

1. Load sentiment dictionary.

SentimentWortschatz, or SentiWS for short, is a publicly available German-language resource for sentiment analysis, opinion mining etc. It lists positive and negative polarity bearing words weighted within the interval of [-1; 1] plus their part of speech tag, and if applicable, their inflections. The current version of SentiWS (v1.8b) contains 1,650 positive and 1,818 negative words, which sum up to 15,649 positive and 15,632 negative word forms incl. their inflections, respectively. It not only contains adjectives and adverbs explicitly expressing a sentiment, but also nouns and verbs implicitly containing one.

sent <- c(
  # positive Wörter
  readLines("dict/SentiWS_v1.8c_Negative.txt",
            encoding = "UTF-8"),
  # negative Wörter
  readLines("dict/SentiWS_v1.8c_Positive.txt",
            encoding = "UTF-8")
) %>% lapply(function(x) {
  # Extrahieren der einzelnen Spalten
  res <- strsplit(x, "\t", fixed = TRUE)[[1]]
  return(data.frame(words = res[1], value = res[2],
                    stringsAsFactors = FALSE))
}) %>%
  bind_rows %>% 
  mutate(word = gsub("\\|.*", "", words) %>% tolower,
         value = as.numeric(value)) %>% 
  # manche Wörter kommen doppelt vor, hier nehmen wir den mittleren Wert
  group_by(word) %>% summarise(value = mean(value)) %>% ungroup

2. Apply the dictionary on the artciles.

We now take each word in each article and assign a sentiment value for that word. I only use articles that have been assigned a topic with a probability of over 90% (gamma > 0.9).

3. Calculate sentiment value by document

calculate sentiment value of document \(d\):

\[ \text{Sentiment}_d = \frac{|\text{Sum of positive tokens}_d| - |\text{Sum of negative tokens}_d|}{\text{Number ot tokens}_d} \]

sentDF.values <- sentDF %>%
  select(document, word, value, polarity) %>%
  mutate(id = row_number()) %>%
  spread(polarity, value) %>%
  group_by(document) %>%
  
  # calculate sum of positive and negative values
  summarise(sum_positive = sum(positive, na.rm = T),
            sum_negative = sum(negative, na.rm = T)) %>%

  # calculate diff
  mutate(sent_diff = abs(sum_positive) - abs(sum_negative)) %>%
  
  # combine with dataframe
  left_join(., df, 
            by = "document") %>%
  # calculate sentiment
  mutate(sentiment = sent_diff / text_length)

Plot Sentiment

1. by topic

p <- sentDF.values %>%
  group_by(topic_name) %>%
  summarise(sentiment = mean(sentiment, na.rm=T),
            obs = n())
  
ggplot(p, aes(reorder(topic_name, sentiment), 
              sentiment,
              label = obs)) +
  geom_col(fill = col[1], alpha = 0.7) +
  geom_text() +
  geom_hline(yintercept = 0, linetype = 2,
             color = "black") +
  coord_flip() +
  labs(x="", y="",
       title = "Grouped Sentiment value",
       fill = "Number of\nObservations") +
  theme(axis.text.y = element_text(size = 10))

2.by site

p <- sentDF.values %>%
  group_by(site) %>%
  summarise(sentiment = mean(sentiment, na.rm=T),
            obs = n())
  
ggplot(p, aes(reorder(site, sentiment), 
              sentiment,
              label = obs)) +
  geom_col(fill = col[3], alpha = 0.7) +
  geom_text() +
  geom_hline(yintercept = 0, linetype = 2,
             color = "black") +
  coord_flip() +
  labs(x="", y="",
       title = "Grouped Sentiment value",
       fill = "Number of\nObservations") +
  theme(axis.text.y = element_text(size = 10))

3. By site and topic

p <- sentDF.values %>%
  group_by(site, topic_name, topic) %>%
  summarise(sentiment = mean(sentiment, na.rm=T),
            obs = n())

ggplot(p, 
       aes(reorder(topic_name, topic), 
           sentiment, label = obs)) +
  geom_col(fill=col[1], alpha = 0.7) +
  geom_text() +
  geom_hline(yintercept = 0, linetype = 2,
             color = "black") +
  coord_flip() +
  facet_wrap(~site, ncol = 7) +
  labs(x="", y="", fill= "Number of\nObservations",
       title = "Sentiment value") +
  theme(axis.text.y = element_text(size=12))

sentDF.values %>%
  filter(topic == 6) %>%
  select(title, sentiment, site) %>%
  htmlTable::htmlTable(align = "l")
title sentiment site
1 Heftiger GroKo-Zoff um Glyphosat-Entscheidung - Seehofer soll von Alleingang gewusst haben -0.000954561101549053 Bild.de
2 Nachtschicht der Unionsspitzen: Wo geht's denn hier zurück nach rechts? - WELT -0.00470754901960784 DIE WELT
3 Treffen der Unionsspitzen: CDU und CSU vor Durchbruch bei Zuwanderung - WELT -0.0053354743083004 DIE WELT
4 Vor dem Gipfel zu einer neuen Großen Koalition: Stimmung ist vergiftet -0.00442064451158107 Bild.de
5 Presseschau zur "Ehe für alle": "Komplette Restentkernung der Union" - FOCUS Online -0.00229613034623218 FOCUS ONLINE
6 Ehe für alle: Was bedeutet das Ergebnis der Abstimmung im Bundestag? -0.00221148648648649 SPIEGEL ONLINE
7 Kritik und Applaus für die Kanzlerin - Familien-Krach bei Merkels Nachwuchs -0.000640507726269315 Bild.de
8 Glyphosat-Genehmigung: Schmidts Solo und die Folgen - SPIEGEL ONLINE - Politik -0.00636744444444444 SPIEGEL ONLINE
9 Merkel im ZDF: Rücktritt? "Nein, das stand nicht im Raum" - WELT -0.00115571913929785 DIE WELT
10 Merkel im ZDF: "Ich fürchte gar nichts" - WELT -0.00115702947845805 DIE WELT
11 "Dresdner Erklärung": Junge Union rechnet nach Wahldebakel mit Angela Merkel ab - WELT 9.99999999999998e-05 DIE WELT
12 Merkels Kurswechsel: Kein klares Nein mehr zur "Ehe für alle" | tagesschau.de -0.000650067294751009 Tagesschau.de
13 Nach Glyphosat-Entscheidung: Merkel rügt Minister Schmidt -0.00433584379358438 Tagesschau.de
14 Ausschuss macht Weg frei: Entscheidung zu Ehe für alle am Freitag -0.0034563025210084 stern.de
15 Debatte um Gleichstellung: Abstimmung zu Ehe für alle - Rechtsausschuss macht Weg frei -0.000796120689655172 stern.de
16 Kritischer Nachwuchs, werbende Kanzlerin: Neue Töne auf dem Weg nach Jamaika 0.000422988505747126 FOCUS ONLINE
17 Seehofer sieht Koalitionsbruch: Koalitionskrach über Ehe für alle verschärft sich -0.000528528528528529 stern.de
18 Abstimmung im Bundestag: Historische Entscheidung zur «Ehe für alle» möglich -0.00314878787878788 stern.de
19 CDU und CSU: Die ÖVP loben, Merkel treffen -0.00479510703363914 ZEIT ONLINE
20 Gleichgeschlechtliche Partnerschaften: Wie Merkel den Weg für die Ehe für alle frei machte -0.00235598755832037 ZEIT ONLINE
21 "Ehe für alle" - Merkels Vorstoß war kein Betriebsunfall, sondern Ergebnis langer Überlegungen -0.004620294599018 FOCUS ONLINE
22 Unions-Spitze überrumpelt: Nach Merkels Rückzieher: Votum zur Ehe für alle am Freitag 0.00210248344370861 stern.de
23 Vor Gipfel mit der CDU: Die neue Flexibilität der CSU beim Begriff "Obergrenze" - WELT -0.00193962900505902 DIE WELT
24 CDU, CSU: Mit diesem Zehn-Punkte-Plan geht Seehofer zu Angela Merkel - WELT -0.00233671742808799 DIE WELT
25 Debatte um Gleichstellung: Koalitionskonflikt um Ehe für alle verschärft sich -0.00178084358523726 stern.de
26 Bundestag wird am Freitag über Ehe für alle abstimmen -0.00215027322404372 Tagesschau.de
27 Ehe für alle: SPD drängt auf namentliche Abstimmung - Union kritisiert Merkel - FOCUS Online -0.00422364990689013 FOCUS ONLINE
28 Junge Union: Der Elefant im Raum -0.000625519848771267 ZEIT ONLINE
29 Historische Entscheidung: Bundestag stimmt Ehe für alle zu -0.00116302681992337 stern.de
30 Ehe für alle: Merkel rückt von CDU-Kurs ab - Abstimmung diese Woche gefordert - FOCUS Online 0.000111740890688259 FOCUS ONLINE
31 "Ehe für alle" im Bundestag: SPD will Abstimmung noch diese Woche | tagesschau.de -4.26804123711341e-05 Tagesschau.de
32 Gleichstellung: Merkel rückt vom Nein der CDU zur Ehe für alle ab 0.00023091286307054 stern.de
33 Bundestagswahlkampf: Merkel rückt vom Nein zur Ehe für alle ab -0.00282320675105485 SPIEGEL ONLINE
34 Streit mit SPD - Schmidt verteidigt Glyphosat-Ja -0.00273404255319149 Tagesschau.de
35 Heidelberg: Angela Merkel bei Wahlkampfauftritt mit Tomaten beworfen -0.00422688172043011 stern.de
36 Asylkompromiss in der Union: Seehofer zitiert zufrieden das Ende des zweiten Absatzes - WELT -0.00133529411764706 DIE WELT
37 Glyphosat: SPD wirft CSU-Minister Vertrauensbruch vor -0.00107538126361656 ZEIT ONLINE
38 Gesetzesreform: Historische Entscheidung: Bundestag beschließt "Ehe für alle" 0.000554203539823009 stern.de
39 Vorfall in Heidelberg: Merkel bei Wahlkampfauftritt mit Tomaten beworfen -0.00494176072234763 stern.de
40 Spahn im BaB: "Dieses Land ist bürgerlich wie lange nicht" 0.00141890660592255 Tagesschau.de
41 Zwischenfall in Heidelberg: Merkel bei Wahlkampfauftritt mit Tomaten beworfen -0.00504348837209302 stern.de
42 Streit um "Ehe für alle": Das sind die Rechte homosexueller Paare in Deutschland -0.000765647058823529 stern.de
43 Zustimmung wahrscheinlich: Bundestag debattiert über Ehe für alle -0.00308503562945368 stern.de
44 Netz-Wut gegen Glyphosat-Minister - CSU-Mann Christian Schmidt unter Beschuss -0.00264306220095694 Bild.de
45 Glyphosat: "Entgleiste Diskussion ist erschreckend" -0.00422409638554217 ZEIT ONLINE
46 Ehe für alle: Merkel nennt Streit über Homo-Ehe traurig und unnötig -0.00337736572890026 SPIEGEL ONLINE
47 Toxisch für die Kanzlerin - CSU-Minister verspritzt Gift über GroKo -0.00488451443569554 Bild.de
48 Merkel rückt rückt von klarem CDU-"Nein" zur Ehe für alle ab - FOCUS Online 0.00085595567867036 FOCUS ONLINE
49 "Ehe für alle": Merkel reagiert genial, aber sie gestaltet nicht 0.000931470588235295 stern.de
50 "Ehe für alle": Merkel reagiert genial, aber sie gestaltet nicht 0.000931470588235295 stern.de
51 Historische Abstimmung: "Ehe für alle": Merkel stimmt mit Nein 9.1566265060241e-05 stern.de
52 Ehe für alle - SPD will Nein-Sager der Union mit namentlicher Abstimmung bloßstellen 0.000203030303030303 FOCUS ONLINE
53 Historische Abstimmung: "Ehe für alle": Merkel stimmt mit Nein 8e-05 stern.de
54 Unions-Kompromiss: Der Streit ist noch nicht zu Ende -0.00241185410334346 Tagesschau.de
55 Ehe für alle: Merkel nennt Streit traurig und unnötig -0.013384858044164 ZEIT ONLINE
56 "Brigitte"-Gespräch: Ehe für alle: Merkel rückt vom klaren Nein der CDU ab -0.000197444089456869 stern.de
57 Ehe für alle: Parlamentarier fordern Abstimmung zu gleichgeschlechtlicher Ehe | ZEIT ONLINE 0.00133064516129032 ZEIT ONLINE
58 Seehofer zum Unions-Kompromiss: "Das geschriebene Wort gilt" - WELT -0.00205822368421053 DIE WELT
59 Merkels Äußerungen zur Ehe für alle: Wenige Sätze, große Wirkung | tagesschau.de -0.000243564356435643 Tagesschau.de
60 Barbara Hendricks will ihrer Partnerin einen Heiratsantrag machen 3.44370860927152e-05 SPIEGEL ONLINE
61 Gleichstellung: Union gibt Abstimmung über «Ehe für alle» im Bundestag frei -0.00312027027027027 stern.de
62 "Ehe für alle": "Durchaus geschickt von Merkel" 0.000108108108108108 Tagesschau.de
63 Hendricks will Glyphosat-Einsatz beschränken -0.00561360544217687 Tagesschau.de
64 CDU-Jugend: JU Düsseldorf fordert "sofortigen Rücktritt" Merkels - WELT -0.0010641638225256 DIE WELT
65 Ehe für alle: Bundestag stimmt am Freitag ab - FOCUS Online 0.00172857142857143 FOCUS ONLINE
66 Migrationspolitik: CSU erbost über Jean-Claude Junckers Lob zum Asylkompromiss - WELT 0.0017275 DIE WELT
67 Abstimmung im Bundestag: Wie Angela Merkel ihr "Nein" zur "Ehe für alle" begründet 0.00212716981132075 stern.de
68 Gleichstellung: Ehe für alle: Bald Abstimmung im Bundestag? -0.000709469696969697 stern.de
69 Abstimmung: «Ehe für alle» im Bundestag: Historischer Beschluss möglich -0.00115984848484848 stern.de
70 Nach "Brigitte Live"-Talk: CDU-Rüge für Kanzlerin wegen "Ehe für alle": "Wir haben die Nase voll" -0.00593166023166023 stern.de
71 Minister Schmidt zu Glyphosat-Zulassung: "Habe die Entscheidung für mich getroffen" - SPIEGEL ONLINE - Politik -0.00302886178861789 SPIEGEL ONLINE
72 Christian Schmidt: Agrarminister wird in sozialen Medien bedroht -0.00148468085106383 ZEIT ONLINE
73 Wahlkampfauftritt auf Rügen: Merkel: SPD soll sich von Rot-Rot-Grün abwenden 0.00184774774774775 stern.de
74 Im Schengen-Raum: Merkel: Können auf Grenzkontrollen nicht verzichten -0.00170542986425339 stern.de
75 Morddrohungen gegen Glyphosat-Minister Christian Schmidt (CSU) -0.00538597285067873 Bild.de
76 Vor Abstimmung im Bundestag: CSU bekräftigt Ablehnung der Ehe für alle -0.00182757009345794 SPIEGEL ONLINE
77 Sicherheitspolitik: Merkel für Verlängerung von Grenzkontrollen -0.001256038647343 stern.de
78 Streit um Glyphosat-Votum: Kanzleramt erinnerte an Abmachung - Minister Schmidt ignorierte sie - SPIEGEL ONLINE - Politik -0.0010015306122449 SPIEGEL ONLINE
79 Weitere Debatte: Ehe für alle: Vorentscheidung im Bundestag? -0.00215987261146497 stern.de
80 Fraktionszwang aufgehoben: Merkel gibt Abstimmung über Ehe für alle in Unionsfraktion frei - FOCUS Online 0.000134228187919463 FOCUS ONLINE
81 Abstimmung über "Ehe für alle" schafft es auf die Tagesordnung - FOCUS Online -0.00433241379310345 FOCUS ONLINE
82 Einigung der Union: Seehofers Preis für die Versöhnung in der Flüchtlingsfrage ist hoch - WELT 0.0078351724137931 DIE WELT
83 Glyphosat-Entscheidung: Merkel rügt Alleingang von CSU-Agrarminister 4.67153284671533e-05 ZEIT ONLINE
84 "Brigitte"-Gespräch: Ehe für alle: Merkel rückt vom klaren Nein der CDU ab 2.35294117647059e-05 stern.de

Radar plot

require(ggiraph)
require(ggiraphExtra)
sentDF.values %>%
  group_by(site, topic_name) %>%
  summarise(sentiment = mean(sentiment, na.rm=T)) %>%
  spread(key=topic_name, value=sentiment) -> radar

radar %>%
  ggRadar(aes(color=site), 
          rescale = F,
          alpha = 0, legend.position = "right") +
  labs(title = "")

for (i in stmOut$settings$covariates$yvarlevels) {

  p <-radar %>%
    filter(site == i) %>%
    ggRadar(rescale = F,
            alpha = 0, legend.position = "right") +
    labs(title = i)
  
  ggsave(p, filename = paste0("../figs/radarchart_",i,k,".png"), 
         device = "png", dpi = 600)
  
}
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image
## Saving 7 x 5 in image

Compare with polls

Define Functions

Prepare Dataframes

Plot